using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using System.Windows.Forms;
using Microsoft.VisualStudio.Text.Tagging;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Editor;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.VCCodeModel;
using System.Text;
using System.Drawing;
using EnvDTE;
using EnvDTE80;
using System.IO;
using System.Reflection;


namespace FunctionBar
{

    // my original plan was to use i0 and i1 to identify a
    // substring that should be highlighted any time the function
    // is selected, as that would match the behavior of XCode's
    // bar.  however, the current version of the bar simply places
    // the cursor at the front of a function definition, thus
    // the value of i1 is never used.  
    //
    // and this is a good thing, because the pattern matching code
    // doesn't always calculate i1.
    public class LuaMatch
    {
        public string name = "";
        public bool local;
        public MatchType type;
        public int i0 = 0, i1 = 0;
        /*
        public LuaMatch(int i0, int i1, string name)
        {
            this.i0 = i0; this.i1 = i1;
            this.name = name;
        }*/
    }

    public class LuaBar : FunBar
    {


        List<LuaMatch> flist = new List<LuaMatch>();

        Regex comment = new Regex(@"(\n|\r|^)[ \t]*--[^\n\r]*\G", RegexOptions.RightToLeft);


        public override uint nentries()
        {
            return (uint)flist.Count;
        }


        public override int GetEntryAttributes(int iCombo, int iIndex, out uint pAttr)
        {
            if (flist[iIndex].type==MatchType.COMMENT)
            {
                pAttr = (uint)DROPDOWNFONTATTR.FONTATTR_BOLD;
                return 0;
            }
            pAttr = 0;
            return 0;
        }

        public override int GetEntryImage(int iCombo, int iIndex, out int piImageIndex)
        {
            piImageIndex = 0;
            if (flist[iIndex].local) piImageIndex = 1;
            if (flist[iIndex].type == MatchType.COMMENT)
            {
                piImageIndex = 2;
                if (flist[iIndex].name == "") piImageIndex = 4;
            }
            if (flist[iIndex].type==MatchType.TABLE) piImageIndex = 3;
            return 0;
        }

        public override int GetEntryText(int iCombo, int iIndex, out string ppszText)
        {
            if (flist[iIndex].local)
            {
                // i think if i'm going to indent local functions, i really
                // need to keep track of function's current scopes.  and that
                // requires more effort than i'm really up for atm.
                //ppszText = "  " + flist[iIndex].name;
                ppszText = flist[iIndex].name;
            }
            else ppszText = flist[iIndex].name;
            return 0;
        }


        //
        // writing reasonable patterns for lua is easier than C++,
        // primarily because i have much lower standards for
        // "correctness" when outlining lua code.  
        //
        // even moderately obscure lua programs can do very odd things,
        // for example, assigning any global variable to a function
        // return value means that the type of that global is unclear. 
        // it could be a function, or a table, or a usertype or
        // whatever else.
        //
        // so rather than worrying too much about getting things right,
        // i've just tried to provide enough pattern matching to create
        // a dropdown bar that will be useful in my own lua code.
        //
        // that means i provide quick navigation to global and local
        // function declared using the "function <name>(" syntactic
        // sugar, and any global variables assigned via table
        // constructors.
        //
        // i allow id's in both cases to contain '.'s, so, it's
        // perfectly possible that something i index as a global table
        // is in fact a global user type.  it's also possible that a
        // function with a name that's a member of a local table will
        // get mistakenly identified as a global.
        //
        // neither of these cases bothers me.
        //
        // even given my relatively modest goals, there's at least one 
        // case that i know i'm not getting right.  because of the way
        // my pattern matching logic works, including a commented out 
        // "local" prior to a function or table will cause it to never
        // be added to the dropdown list.  for example, this function 
        // is effectively invisible:
        //
        // --local
        // function bob() end
        //
        // that does bother me a little.
        void get_function_positions()
        {
            //
            // crude regex for getting function definitions.
            //
            Regex r = new Regex(@"(local)?\s*(function)\s+([\w\.]+)\s*");

            Match m = r.Match(file_data);

            //string groups = m.Groups.ToString();
            while (m.Success)
            {
                LuaMatch toadd = new LuaMatch();

                toadd.local = m.Groups[1].Value!="";

                toadd.name =  m.Groups[3].Value;
                toadd.type = MatchType.FUNCTION;
                toadd.i0= m.Index;
                if(!toadd.local) {
                    toadd.i0=m.Groups[2].Index;
                }

                int i = m.Index;

                m = m.NextMatch();

                //make sure the function definition isn't inside an inline comment.
                Match m3 = comment.Match(file_data, i+1);
                if (m3.Success) continue;

                flist.Add(toadd);

            }
        }


        public void add_notes()
        {
            Regex r = new Regex(@"(\n|\r)([ \t]*)---*!!([^\n\r]*)");
            Match m = r.Match(file_data);

            while (m.Success)
            {
                LuaMatch toadd = new LuaMatch();

                toadd.name = m.Groups[3].Value;

                toadd.name = toadd.name.Replace("\t", "  ");

                toadd.i0 = m.Groups[2].Index;
                m = m.NextMatch();

                toadd.type = MatchType.COMMENT;
                insert_function(toadd);
            }

        }

        void add_tables()
        {
            Regex r = new Regex(@"(local)?\s*([\w\.]+)\s*=\s*{");
            Match m = r.Match(file_data);

            while (m.Success)
            {
                
                LuaMatch toadd = new LuaMatch();

                toadd.local = false;
                toadd.name = m.Groups[2].Value;
                toadd.type = MatchType.TABLE;
                toadd.i0 = m.Index;
                if (!toadd.local)
                {
                    toadd.i0 = m.Groups[2].Index;
                }

                int i = m.Index;

                m = m.NextMatch();

                // only add global tables to the dropdown bar
                if (toadd.local) continue;

                //make sure the function definition isn't inside an inline comment.
                Match m3 = comment.Match(file_data, i);
                if (m3.Success) continue;

                insert_function(toadd);

            }
        }

        int insert_function(LuaMatch fun)
        {
            int k;
            for (k = 0; k < flist.Count; k++)
            {
                if (flist[k].i0 > fun.i0)
                {
                    flist.Insert(k, fun);
                    return k;
                }
            }

            flist.Add(fun);
            return k;
        }

        public override void refresh_functions()
        {
            flist.Clear();

            if (file_data == null)
            {
                return;
            }

            get_function_positions();

            add_tables();

            // do another pass through the file, and remove anything that
            // looks like it's inside a /* */ comment.

            if (flist.Count > 0)
            {
                //delete_comments();
            }

            // do this after deleting commented functions -- as notes can be commented.
            add_notes();

            bar.RefreshCombo(0, 0);

            calc_caret_pos();
        }


        public override int OnItemChosen(int iCombo, int iIndex)
        {

            if (null == vsTextView) return 0;

            int r, c;
            vsTextView.GetLineAndColumn(flist[iIndex].i0, out r, out c);

            vsTextView.CenterLines(r, 1);
            vsTextView.CenterColumns(r, c, 1);
            vsTextView.SetCaretPos(r, c);

            vsTextView.SendExplicitFocus();

            return 0;
        }

        public override void calc_caret_pos()
        {
            int r, c, caret, vspace;

            vsTextView.GetCaretPos(out r, out c);
            vsTextView.GetNearestPosition(r, c, out caret, out vspace);

            last_caret_pos = caret;

            int i;
            for (i = 0; i < flist.Count; i++)
            {
                if (flist[i].i0 > caret)
                {
                    break;
                }
            }
            i--;

            if (i >= 0) bar.SetCurrentSelection(0, i);
        }


    }

}